using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.Xml.Linq;



namespace BloodBot
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class Enemy : Microsoft.Xna.Framework.GameComponent, ConfigItem
    {
        //State fields
        enum behaviourState
        {
            Idle, Wander, Seek, Flee, Attack
        }
        
        SpriteBatch spriteBatch;
        SpriteFont font;
        private Model enemyModel;
        private BasicEffect effect;
        private float distance;

        private Vector3 movePoint1, movePoint2;
        private Vector3 target;
        private Stopwatch idleTimer;
        private float lerpCounter = 0;

        public BoundingSphere boundingSphere;

        public Matrix enemyTranslation { get; set; }
        public Matrix enemyRotation { get; set; }
        public Matrix enemyScale { get; set; }
        public Vector3 position { get; set; }
        public Vector3 direction;
        public bool getNewMovePt = true;
        public int indexNo;
        behaviourState currentBehaviourState;
        private Pathfinding pathfinding;
        private LinkedList<Cell> path;

        //Configurable fields
        private const float GroundPosition = -0.75f;
        private float speed = 3f;
        private int totalHealth = 200;
        private float size;
        public int health { get; set; }



        public Enemy(Game game, Vector3 Position, float size, int indexNo, BasicEffect effect, SpriteFont font, SpriteBatch spriteBatch, Pathfinding pathfinding)
            : base(game)
        {
            this.position = Position;
            this.health = totalHealth;
            this.size = size;
            this.indexNo = indexNo;
            this.enemyTranslation = Matrix.CreateTranslation(position);
            this.effect = effect;
            this.font = font;
            this.spriteBatch = spriteBatch;
            this.pathfinding = pathfinding;
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            this.enemyTranslation = Matrix.Identity;
            this.enemyRotation = Matrix.Identity;
            this.enemyScale = Matrix.Identity;

            path = new LinkedList<Cell>();

            currentBehaviourState = behaviourState.Idle;
            
            boundingSphere = new BoundingSphere(position, size);

            enemyScale = Matrix.CreateScale(size);

            base.Initialize();
        }

        public void Load(Collisions collisions)
        {
            idleTimer = new Stopwatch();

            enemyModel = Game.Content.Load<Model>("Models\\BloodCell-Spiky");
       }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public void Update(GameTime gameTime, Player player, Random random, float groundSize, Collisions collisions, Map map, Sounds sound)
        {
            double lastUpdate = gameTime.ElapsedGameTime.TotalMilliseconds;
            float updateAmount = speed * (float)(lastUpdate / 1000f);
            float damageAmount = 35 * (float)(lastUpdate / 1000f);
            //Console.Out.WriteLine("Damage amount: " + damageAmount + "; lastUpdate: " + lastUpdate);
            //Idle Timer
            if (!idleTimer.IsRunning)
            {
                currentBehaviourState = behaviourState.Idle;
                idleTimer.Start();
            }
            //Idle Timer
            if (idleTimer.Elapsed.Seconds >= 5)
            {
                //wander = true;
                currentBehaviourState = behaviourState.Wander;
                idleTimer.Restart();
            }

            //Check distance from player
            distance = Vector3.Distance(position, player.pos);

            if (distance <= 1.5)
            {
                currentBehaviourState = behaviourState.Attack;
                player.adjustHealth(-damageAmount);

                sound.PlayEnemyAttackSound();
            }

            //Seek
            //Move towards the player and follow him, if he is close
            if (distance <= groundSize * 0.8 && distance >= 1.5 && health >= totalHealth / 4)
            {
                currentBehaviourState = behaviourState.Seek;

                Cell startPoint = map.VectorToCell(position);
                Cell destination = map.VectorToCell(player.pos);
                if (destination != null && destination.getType().Equals("Ground"))
                {
                    path = pathfinding.NavigateTo(startPoint, destination);
                    if (path != null)
                    {
                        foreach (Cell cell in path)
                        {
                            cell.setIsPath(true);
                        }
                    }

                }


                //wander = false;
                getNewMovePt = true;
                if (path != null && path.Count > 0)
                {
                    if (map.VectorToCell(position) == path.First())
                    {
                        path.First().setIsPath(false);
                        path.RemoveFirst();
                    }
                    if (path != null && path.Count > 0)
                    {
                        direction = path.First().pos - position;
                        direction.Normalize();

                        position += direction * updateAmount;
                    }
                }

                if (collisions.CheckCollison(position, new Vector3(0, GroundPosition, 0), (float)(groundSize - 0.5), null))
                {
                    position -= direction * updateAmount;
                }

                target = player.pos;
                enemyRotation = LookAt(position, target);
            }
            //Flee
            //Move away from the player because of low health
            else if (distance <= groundSize / 2 && health <= totalHealth / 4)
            {
                currentBehaviourState = behaviourState.Flee;
                //wander = false;
                getNewMovePt = true;
                direction = player.pos - position;
                direction.Normalize();

                position -= direction * updateAmount;

                if (collisions.CheckCollison(position, new Vector3(0, GroundPosition, 0), (float)(groundSize - 0.5), null))
                {
                    position += direction * updateAmount;
                }

                target = player.pos;
                enemyRotation = LookAt(position, target, true);
            }
            //Wander
            //Move between two random points
            if (currentBehaviourState == behaviourState.Wander)
            {
                if (getNewMovePt)
                {
                    getNewMovePt = false;
                    MovePoints(random, groundSize);

                    target = movePoint2;
                    enemyRotation = LookAt(position, target);
                }

                direction = movePoint2 - movePoint1;
                direction.Normalize();

                position += direction * updateAmount;

                if (collisions.CheckCollison(position, new Vector3(0, GroundPosition, 0), (float)(groundSize - 0.5), null))
                {
                    getNewMovePt = true;
                    position -= direction * updateAmount;
                }
            }

            //Keep enemy on the ground
            position = new Vector3(position.X, GroundPosition + size, position.Z);
            enemyTranslation = Matrix.CreateTranslation(position);
            boundingSphere.Center = position;
  
            base.Update(gameTime);
        }

        public void MovePoints(Random random, float groundSize, bool collision = false)
        {
            if (!collision)
            {
                movePoint1 = position;
                movePoint2 = new Vector3(random.Next((int)-groundSize, (int)groundSize), GroundPosition, random.Next((int)-groundSize, (int)groundSize));
            }
            else
            {
                Vector3 temp = movePoint1;

                movePoint1 = movePoint2;
                movePoint2 = temp;

                target = movePoint2;
                enemyRotation = LookAt(position, target);
            }
        }

        public Matrix LookAt(Vector3 position, Vector3 lookat, bool swap = false)
        {
            //Make enemy look in the direction they are moving
            Matrix rotation = Matrix.Identity;
            lookat.Y = GroundPosition;

            if (swap)
            {
                rotation.Forward = Vector3.Normalize(lookat - position);
            }
            else
            {
                rotation.Forward = Vector3.Normalize(position - lookat);
            }

            rotation.Right = Vector3.Normalize(Vector3.Cross(rotation.Forward, Vector3.Up));
            rotation.Up = Vector3.Normalize(Vector3.Cross(rotation.Right, rotation.Forward));

            return rotation;
        }

        public void TakeDamage(int damageTaken)
        {

            //Deals Damage
            lerpCounter += ((float)damageTaken / 200f);           
            health-= damageTaken;
            //myColour = Color.Lerp(Color.Red, Color.DarkRed, lerpCounter);            
        }

        //Draw current state above enemy
        public void DrawStateText(Camera camera)
        {
            //The general idea should just be to take the player position, add an offset to it to generate the location in object
            //space that you want the text to appear (i.e. add vec3(0,1.5,0) or whatever size is necessary). Then project that point 
            //using your model/view/projection matrices, giving a clip space position. Next you normalize by dividing the vector by its w value, 
            //and scale the position according to the resolution of the display - which gives the final pixel location that you want to put your text on to.

            Vector3 text3DPosition = new Vector3(position.X, position.Y + 0.75f, position.Z);
                        
            Vector3 text2DPosition = Game.GraphicsDevice.Viewport.Project(text3DPosition, camera.projection, camera.view, Matrix.Identity);

            string stateText;

            switch (currentBehaviourState)
            {
                case behaviourState.Idle:
                    stateText = "Idle";
                    break;
                case behaviourState.Wander:
                    stateText = "Wander";
                    break;
                case behaviourState.Seek:
                    stateText = "Seek";
                    break;
                case behaviourState.Flee:
                    stateText = "Flee";
                    break;
                case behaviourState.Attack:
                    stateText = "Attack";
                    break;
                default:
                    stateText = "";
                    break;
            }

            //BoundingFrustum frustrum = new BoundingFrustum(camera.view * camera.projection);

            if (camera.frustrum.Contains(boundingSphere) != ContainmentType.Disjoint)
            {
                spriteBatch.Begin();
                spriteBatch.DrawString(font, stateText, new Vector2(text2DPosition.X, text2DPosition.Y), Color.White);
                spriteBatch.End();
            }
        }

        public void Draw(Camera camera)
        {
            // Copy any parent transforms.
            Matrix[] transforms = new Matrix[enemyModel.Bones.Count];
            enemyModel.CopyAbsoluteBoneTransformsTo(transforms);

            // Draw the model. A model can have multiple meshes, so loop.
            foreach (ModelMesh mesh in enemyModel.Meshes)
            {
                // This is where the mesh orientation is set, as well 
                // as our camera and projection.
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.World = transforms[mesh.ParentBone.Index] * enemyScale * enemyRotation * enemyTranslation;
                    effect.View = camera.view;
                    effect.Projection = camera.projection;
                }
                // Draw the mesh, using the effects set above.
                mesh.Draw();
            }

            foreach (Cell cell in path)
            {
                cell.setIsPath(false);
            }
        }

        // TODO: finish implementing
        public void LoadConfig()
        {
            var doc = XElement.Load("filename");
            var enemyValues = doc.Element("enemy").Elements();
            var enemy = doc.Element("enemy");
            var health = (int)enemy.Element("health");
            var speed = (int)enemy.Element("speed");
        }
    }
}
